#include<cstdint>
#include<cstring>
#include"sm3.h"

#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define SM3_TO_BIGENDIAN64(x) __builtin_bswap64(x)
#define SM3_TO_BIGENDIAN32(x) __builtin_bswap32(x)
#else
#define SM3_TO_BIGENDIAN64(x) x
#define SM3_TO_BIGENDIAN32(x) x
#endif

namespace SM3 {
const static uint32_t IV[] = {
	0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600,
	0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e
};

constexpr uint32_t T(uint8_t j) {
	if(j<=15)	return 0x79cc4519;
	else		return 0x7a879d8a;
}

constexpr uint32_t FF(uint8_t j, uint32_t X, uint32_t Y, uint32_t Z) {
	if(j<=15)	return X^Y^Z;
	else		return (X&Y) | (X&Z) | (Y&Z);
}

constexpr uint32_t GG(uint8_t j, uint32_t X, uint32_t Y, uint32_t Z) {
	if(j<=15)	return X^Y^Z;
	else		return (X&Y) | ((~X)&Z);
}

constexpr uint32_t P0(uint32_t X) {
	return X ^ ((X<<9) | (X>>23)) ^ ((X<<17) | (X>>15));
}

constexpr uint32_t P1(uint32_t X) {
	return X ^ ((X<<15) | (X>>17)) ^ ((X<<23) | (X>>9));
}

uint64_t padding(uint8_t *msg, uint64_t len) {
//	int k = ((448-len*8-8)%512+512)%512;
	int k = ((56-len-1)%64+64)%64;
	uint64_t nlen = len;
	msg[nlen++] = 0x80;	//0b 1000 0000
	memset(msg+nlen, 0, k);
	nlen+=k;
	len = SM3_TO_BIGENDIAN64(len*8);
	memcpy(msg+nlen, (uint8_t*) &len, sizeof(uint64_t));
	return nlen+sizeof(uint64_t);
}

SM3::SM3()/*:
	A(IV[0]), B(IV[1]), C(IV[2]), D(IV[3]),
	E(IV[4]), F(IV[5]), G(IV[6]), H(IV[7])*/
{ }

void SM3::init() {
	A=IV[0], B=IV[1], C=IV[2], D=IV[3],
	E=IV[4], F=IV[5], G=IV[6], H=IV[7];
}

void SM3::IT(const uint8_t *m, uint64_t len) {
	for(uint64_t i=0; i<len; i+=64) {
		const uint8_t *msg = m+i;
		uint32_t W[68], W_[64];
		for(int j=0; j<16; j++) {
			W[j] = 
				(msg[j*4]  <<24) | (msg[j*4+1]<<16) | 
				(msg[j*4+2]<<8)  | msg[j*4+3];
		}
		for(int j=16; j<68; j++) {
			W[j] = P1(W[j-16] ^ W[j-9] ^ 
					((W[j-3]<<15) | (W[j-3]>>17))) ^
					((W[j-13]<<7) | (W[j-13]>>25)) ^
					W[j-6];
		}
		for(int j=0; j<64; j++) {
			W_[j]= W[j]^W[j+4];
		}
		CF(W,W_);
	}
}

void SM3::CF(uint32_t W[64], uint32_t W_[64]) {
	uint32_t SS1, SS2, TT1, TT2;
	uint32_t OA, OB, OC, OD, OE, OF, OG, OH;
	OA = A, OB = B, OC = C, OD = D, OE = E, OF = F, OG = G, OH = H;
	for(int j=0; j<64; j++) {
		uint32_t Tj = T(j);
		SS1 = ((A<<12) | (A>>20)) + E;
		int jj=j%32;
		if(jj==0)	SS1 += Tj;
		else		SS1 += ((Tj<<jj) | (Tj>>(32-jj)));
		SS1 = (SS1<<7) + (SS1>>25);
		SS2 = SS1 ^ ((A<<12) | (A>>20));
		TT1 = FF(j, A, B, C) + D + SS2 + W_[j];
		TT2 = GG(j, E, F, G) + H + SS1 + W[j];
		D = C;
		C = (B<<9) | (B>>23);
		B = A;
		A = TT1;
		H = G;
		G = (F<<19) | (F>>13);
		F = E;
		E = P0(TT2);
	}
	A^=OA, B^=OB, C^=OC, D^=OD, E^=OE, F^=OF, G^=OG, H^=OH;
}

void SM3::hash(void *msg, uint64_t len) {
	init();
	len=padding((uint8_t*)msg, len);
	IT((uint8_t*)msg, len);
	//memset((uint8_t*)msg, 0xff, len);
	uint32_t tA=SM3_TO_BIGENDIAN32(A);
	uint32_t tB=SM3_TO_BIGENDIAN32(B);
	uint32_t tC=SM3_TO_BIGENDIAN32(C);
	uint32_t tD=SM3_TO_BIGENDIAN32(D);
	uint32_t tE=SM3_TO_BIGENDIAN32(E);
	uint32_t tF=SM3_TO_BIGENDIAN32(F);
	uint32_t tG=SM3_TO_BIGENDIAN32(G);
	uint32_t tH=SM3_TO_BIGENDIAN32(H);
	memcpy((uint8_t*)msg,                    (uint8_t*)&tA, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t),   (uint8_t*)&tB, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*2, (uint8_t*)&tC, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*3, (uint8_t*)&tD, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*4, (uint8_t*)&tE, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*5, (uint8_t*)&tF, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*6, (uint8_t*)&tG, sizeof(uint32_t));
	memcpy((uint8_t*)msg+sizeof(uint32_t)*7, (uint8_t*)&tH, sizeof(uint32_t));
}


}

